package cn.demoncat.util.io;

import cn.demoncat.util.exception.IoRuntimeException;
import cn.demoncat.util.lang.ReflectClassUtil;
import cn.demoncat.util.lang.constant.CharsetConstant;
import cn.demoncat.util.lang.constant.StringConstant;
import org.apache.commons.io.FileUtils;
import org.apache.commons.io.FilenameUtils;
import org.apache.commons.io.filefilter.FalseFileFilter;
import org.apache.commons.io.filefilter.IOFileFilter;
import org.apache.commons.io.filefilter.RegexFileFilter;
import org.apache.commons.io.filefilter.TrueFileFilter;
import org.apache.commons.lang3.StringUtils;

import java.io.*;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import java.util.Objects;
import java.util.stream.Collectors;

/**
 * 文件处理：获取、移动复制替换、读写、流
 * 
 * @author 延晓磊
 *
 * @since 2016年12月13日
 */
public final class FileUtil {
 
	/**
	 * Eclipse生成文件时，产生的BOM标记（使用文件时会报乱码错误）
	 */
	private static final String ECLIPSE_BOM_UNI = "\65279";
	private static final String ECLIPSE_BOM = "ï»¿";
 
	//################################ 文件管理 ################################//
	
	/**
	 * 获取根目录下的所有目录/磁盘的File
	 * 
	 * @return 所有一级目录，如Linux中的/opt ，Windows中的C:\
	 */
	public static File[] listRoot(){
		return File.listRoots();
	}
	/**
	 * 获取文件所在磁盘的File
	 * 
	 * @param file 完整路径/相对路径(相对于ClassPath)
	 * @return Windows(C:/)、Linux(/volume , ~/)
	 */
	public static File getDisk(File file){
		if (file == null) {
			return null;
		}
		//根据完整路径名获取磁盘前缀名称：D:\ 或  ~/
		String prefix = FilenameUtils.getPrefix(file.getAbsolutePath());
		return new File(prefix);
	}
	
	/**
	 * 获取target/class所在目录
	 *
	 * 注意：jar包内的文件，无法直接用File读取
	 *
	 * @return D:/Data/webapps/javas/demoncat-util/target/classes/ 或 /webapps/demoncat-util/demoncat-util-web-0.0.1-SNAPSHOT.jar!/BOOT-INF/classes!/
	 */
	public static String getClassPath(){
		return ReflectClassUtil.getClassPath();
	}

	/**
	 * 获取文件的target/class路径
	 *
	 * 注意：jar包内的文件，无法直接用File读取
	 *
	 * @param path	文件路径，如 a/b.txt
	 *
	 * @return 资源的绝对路径，资源不存在返回null：E:/webapps/java/demoncat-test/target/test-classes/test/cert/ 或 /webapps/tt-dz-pay-web/tt-dz-pay-web-0.0.1-SNAPSHOT.jar!/BOOT-INF/classes!/test/cert/
	 */
	public static String getClassPath(String path){
		return ReflectClassUtil.getResourceUrl(path);
	}

	/**
	 * 判断文件是否存在
	 *
	 * @param path
	 * @return
	 */
	public static boolean exists(String path){
		return new File(path).exists();
	}

	/**
	 * 删除文件/目录(递归删除)
	 * 
	 * @param file
	 * @return 如果文件为空或不存在则返回false
	 * @throws IoRuntimeException 如果删除失败则抛出
	 */
	public static boolean delFile(File file){
		//不存在则忽略
		if (file == null || ! file.exists()) {
			return false;
		}
		//强制删除文件，如果是目录则递归删除
		try{
			FileUtils.forceDelete(file);
			return true;
		} catch (IOException e) {
			throw new IoRuntimeException("删除文件失败", e);
		}
	}

	/**
	 * 删除文件/目录(递归删除)
	 *
	 * @param file
	 * @return 如果文件为空或不存在则返回false
	 * @throws IoRuntimeException 如果删除失败则抛出
	 */
	public static boolean delFile(String file){
		return delFile(new File(file));
	}
	
	/**
	 * 创建目录，包含必要的祖目录
	 * 
	 * @param dir
	 * @return 如果目录为空或不存在则返回false
	 * @throws IoRuntimeException  如果创建失败则抛出
	 */
	public static boolean addDir(File dir){
		//为空或已存在则忽略
		if (dir == null || dir.exists()) {
			return false;
		}
		//强制创建目录，包含必要的父目录
		try {
			FileUtils.forceMkdir(dir);
			return true;
		} catch (IOException e) {
			throw new IoRuntimeException("创建目录失败："+ e.getMessage(), e);
		}
	}
	/**
	 * 创建文件(目录或文件)的父目录，包含必要的祖目录
	 * 
	 * @param  file
	 * @return 如果文件为空，或者父目录已存在则返回false
	 * @throws IoRuntimeException   如果创建失败则抛出
	 */
	public static boolean addParentDir(File file){
		//为文件空或父目录已存在则忽略
		if (file == null || file.getParentFile().exists()) {
			return false;
		}
		//强制创建父目录，包含必要的祖目录
		try {
			FileUtils.forceMkdirParent(file);
			return true;
		} catch (IOException e) {
			throw new IoRuntimeException("创建目录失败："+ e.getMessage(), e);
		}
	}		
	/**
	 * 复制文件(文件或目录)到目录，如果目录不存在则创建，如果目录中已存在文件则替换，如果文件为目录则递归复制
	 * 
	 * @param file 
	 * @param dir
	 * @return 如果文件或目录为空则返回false，如果文件不存在则返回false，如果目录非目录则返回false
	 * @throws IoRuntimeException  如果复制失败则抛出
	 */
	public static boolean copyToDir(File file, File dir){
		//参数为空时忽略
		if (file == null || dir == null) {
			return false;
		}
		//文件不存在时忽略
		if (! file.exists()) {
			return false;
		}
		//目录非目录时忽略
		if (dir.exists() && ! dir.isDirectory()) {
			return false;
		}
		try{
			//如果目录存在且目录中已存在文件则先删除
			if (dir.exists() && FileUtils.directoryContains(dir, file)) {
				delFile(file);
			}
			if (file.isDirectory()) {
				//复制目录到目标目录：递归复制目录中的内容
				FileUtils.copyDirectoryToDirectory(file, dir);
			}else{
				//复制文件到目录：目录不存在则创建，存在则将文件添加到目录中 
				FileUtils.copyFileToDirectory(file, dir);
			}
			return true;
		}catch(Exception e){
			throw new IoRuntimeException("复制文件到目录失败："+ e.getMessage(), e);
		}
	}
	/**
	 * 移动文件(文件或目录)到目录，如果目录不存在则创建，如果目录中已存在文件则替换，如果文件为目录则递归复制
	 * 
	 * @param file 
	 * @param dir
	 * @return 如果文件或目录为空则返回false，如果文件不存在则返回false，如果目录非目录则返回false
	 * @throws IoRuntimeException  如果复制失败则抛出
	 */
	public static boolean moveToDir(File file, File dir){
		//参数为空时忽略
		if (file == null || dir == null) {
			return false;
		}
		//文件不存在时忽略
		if (! file.exists()) {
			return false;
		}
		//目录非目录时忽略
		if (dir.exists() && ! dir.isDirectory()) {
			return false;
		}
		try{
			//如果目录存在且目录中已存在文件则先删除
			if (dir.exists() && FileUtils.directoryContains(dir, file)) {
				delFile(file);
			}
			if (file.isDirectory()) {
				//移动目录到目标目录：递归移动目录中的内容
				FileUtils.moveDirectoryToDirectory(file, dir,true);
			}else{
				//移动文件到目录：目录不存在则创建，存在则将文件添加到目录中 
				FileUtils.moveFileToDirectory(file, dir,true);
			}
			return true;
		}catch(Exception e){
			throw new IoRuntimeException("移动文件到目录失败："+ e.getMessage(), e);
		}
	}
	/**
	 * 复制文件(文件或目录)并重命名，如果目标文件存在则删除，如果文件为目录则递归复制
	 * 
	 * @param file 
	 * @param descFile
	 * @return 如果文件为空或不存在则返回false
	 * @throws IoRuntimeException  如果复制失败则抛出
	 */
	public static boolean copy(File file, File descFile){
		//参数为空或不存在时忽略
		if (file == null || ! file.exists()) {
			return false;
		}
		//如果目标文件存在则删除，防止文件和目录的类型冲突
		if (descFile != null && ! descFile.exists()) {
			delFile(descFile);
		}
		//复制文件并重命名
		try{
			if (file.isDirectory()) {
				//复制目录：递归复制目录中的内容
				FileUtils.copyDirectory(file, descFile);
			}else{
				//复制文件
				FileUtils.copyFile(file, descFile);
			}
			return true;
		}catch(Exception e){
			throw new IoRuntimeException("复制文件并重命名失败："+ e.getMessage(), e);
		}
	}
	/**
	 * 移动文件(文件或目录)并重命名，如果目标文件存在则删除，如果文件为目录则递归移动
	 * 
	 * @param file 
	 * @param descFile
	 * @return 如果文件为空或不存在则返回false
	 * @throws IoRuntimeException  如果移动失败则抛出
	 */
	public static boolean move(File file, File descFile){
		//参数为空或不存在时忽略
		if (file == null || ! file.exists()) {
			return false;
		}
		//如果目标文件存在则删除，防止文件和目录的类型冲突
		if (descFile != null && ! descFile.exists()) {
			delFile(descFile);
		}
		//移动文件并重命名
		try{
			if (file.isDirectory()) {
				//移动目录：递归复制目录中的内容
				FileUtils.moveDirectory(file, descFile);
			}else{
				//移动文件
				FileUtils.moveFile(file, descFile);
			}
			return true;
		}catch(Exception e){
			throw new IoRuntimeException("移动文件并重命名失败："+ e.getMessage(), e);
		}
	}
	/**
	 * 判断目录下是否包含文件(文件或目录)
	 * 
	 * @param dir 
	 * @param childFile
	 * @return 如果目录和文件为空或不存在则返回false，如果目录非目录返回false
	 * @throws IoRuntimeException  如果判断失败则抛出
	 */
	public static boolean contains(File dir, File childFile){
		//参数为空或不存在时忽略
		if (dir == null || childFile == null || ! dir.exists() || ! childFile.exists()) {
			return false;
		}
		//目录非目录
		if (! dir.isDirectory()) {
			return false;
		}
		// 判断目录下是否包含子文件
		try{
			return FileUtils.directoryContains(dir, childFile);
		}catch(Exception e){
			throw new IoRuntimeException("判断目录下是否包含文件失败："+ e.getMessage(), e);
		}
	}

	/**
	 * 获取目录下的目录
	 *
	 * @param dir
	 * @return
	 * @author 延晓磊
	 * @since 2023年04月23日
	 */
	public static List<File> listDirs(File dir){
		return Arrays.stream(Objects.requireNonNull(dir.listFiles())).filter(File::isDirectory).collect(Collectors.toList());
	}

	/**
	 * 清空文件夹
	 *
	 * @param dir
	 *
	 * @author 延晓磊
	 * @since 2023年04月23日
	 */
	public static void clearDir(File dir){
		try {
			FileUtils.cleanDirectory(dir);
		} catch (IOException e) {
			throw new IoRuntimeException("清空目录失败："+ e.getMessage(), e);
		}
	}

	/**
	 * 重命名目录/文件（移动）
	 *
	 * @param src		源文件，必须存在
	 * @param desc		目标文件，不能存在
	 *
	 * @author 延晓磊
	 * @since 2023年04月23日
	 */
	public static void rename(File src, File desc){
		if (src.isFile()) {
			renameFile(src, desc);
		}else{
			renameDir(src, desc);
		}
	}

	/**
	 * 重命名目录（移动）
	 *
	 * @param src	源目录，必须存在
	 * @param desc	目标目录，不能存在
	 *
	 * @author 延晓磊
	 * @since 2023年04月23日
	 */
	public static void renameDir(File src, File desc){
		try {
			FileUtils.moveDirectory(src, desc);
		} catch (IOException e) {
			throw new IoRuntimeException("重命名目录失败："+ e.getMessage(), e);
		}
	}

	/**
	 * 重命名文件（移动）
	 *
	 * @param src	源文件，必须存在
	 * @param desc	目标文件，不能存在
	 *
	 * @author 延晓磊
	 * @since 2023年04月23日
	 */
	public static void renameFile(File src, File desc){
		try {
			FileUtils.moveFile(src, desc);
		} catch (IOException e) {
			throw new IoRuntimeException("重命名文件失败："+ e.getMessage(), e);
		}
	}

	/**
	 * 获取目录下的文件
	 * 
	 * @param dir
	 * @return
	 * @author 延晓磊
	 * @since 2023年04月23日
	 */
	public static List<File> listFiles(File dir){
		return Arrays.stream(Objects.requireNonNull(dir.listFiles())).filter(File::isFile).collect(Collectors.toList());
	}



	/**
	 * 根据指定后缀遍历获取目录下的所有的子文件(不包含目录，筛选指定后缀名的文件，指定递归{子文件中指定后缀的文件/递归文件中指定后缀的所有文件})
	 * 
	 * @param dir
	 * @param isRecursion 是否递归
	 * @param extensions 指定文件扩展名(如"java","txt")，可以指定多个，所有文件传参为null
	 * @return 如果目录为空、不存在、非目录则返回null 
	 * @throws IoRuntimeException  如果获取失败则抛出
	 */
	public static Collection<File> listFiles(File dir, boolean isRecursion, String... extensions){
		//目录为空、不存在、非目录返回null
		if (dir == null || !dir.exists() || !dir.isDirectory()) {
			return null;
		}
		//获取目录下指定递归的文件中指定后缀的文件
		try{
			return FileUtils.listFiles(dir, extensions, isRecursion);
		}catch(Exception e){
			throw new IoRuntimeException("根据后缀名获取目录下的文件失败："+ e.getMessage(), e);
		}
	}
	/**
	 * 根据指定正则遍历获取目录下的所有的子文件(不包含目录，筛选指定正则的文件，指定递归{子文件中指定后缀的文件/递归文件中指定后缀的所有文件})
	 * 
	 * @param dir
	 * @param regex 文件名正则
	 * @param isRecursion 是否递归
	 * @return 如果目录为空、不存在、非目录则返回null 
	 * @throws IoRuntimeException  如果获取失败则抛出
	 */
	public static Collection<File> listFiles(File dir, String regex, boolean isRecursion){
		//目录为空、不存在、非目录返回null
		if (dir == null || !dir.exists() || !dir.isDirectory()) {
			return null;
		}
		//获取目录下指定递归的文件中指定正则的文件
		try{
			return FileUtils.listFiles(dir, new RegexFileFilter(regex), isRecursion ? TrueFileFilter.TRUE : FalseFileFilter.FALSE);
		}catch(Exception e){
			throw new IoRuntimeException("根据正则获取目录下的文件失败："+ e.getMessage(), e);
		}
	}
	/**
	 * 根据指定文件过滤器和目录过滤器遍历获取目录下的所有的子文件(不包含目录)
	 * 
	 * @param dir
	 * @param fileFilter 文件过滤器
	 * @param dirFilter  目录过滤器{TrueFileFilter.TRUE 递归: FalseFileFilter.FALSE 不递归}
	 * @return 如果目录为空、不存在、非目录则返回null 
	 * @throws IoRuntimeException 如果获取失败则抛出
	 */
	public static Collection<File> listFiles(File dir, IOFileFilter fileFilter, IOFileFilter dirFilter){
		//目录为空、不存在、非目录返回null
		if (dir == null || !dir.exists() || !dir.isDirectory()) {
			return null;
		}
		//根据文件过滤器和目录过滤器获取子文件
		try{
			return FileUtils.listFiles(dir, fileFilter, dirFilter);
		}catch(Exception e){
			throw new IoRuntimeException("获取目录下的文件失败："+ e.getMessage(), e);
		}
	}
	
	/**
	 * 根据指定文件过滤器和目录过滤器遍历获取目录下的文件和目录
	 * 
	 * @param dir
	 * @param fileFilter 文件过滤器
	 * @param dirFilter  目录过滤器
	 * @return 如果目录为空、不存在、非目录则返回null 
	 * @throws IoRuntimeException 如果获取失败则抛出
	 */
	public static Collection<File> list(File dir, IOFileFilter fileFilter, IOFileFilter dirFilter){
		//目录为空、不存在、非目录返回null
		if (dir == null || !dir.exists() || !dir.isDirectory()) {
			return null;
		}
		//根据文件过滤器和目录过滤器获取子文件
		try{
			return FileUtils.listFilesAndDirs(dir, fileFilter, dirFilter);
		}catch(Exception e){
			throw new IoRuntimeException("获取目录下的文件和目录失败："+ e.getMessage(), e);
		}
	}

	/**
	 * 获取目录下的文件和目录
	 *
	 * @param dir
	 * @return 如果目录为空、不存在、非目录则返回null 
	 * @throws IoRuntimeException 如果获取失败则抛出
	 */
	public static Collection<File> list(File dir){
		return list(dir, TrueFileFilter.TRUE, TrueFileFilter.TRUE);
	}
	
	/**
	 * 将目录内的所有子文件(不递归，不包含目录)，统一重命名为 count++ ...
	 * 
	 * @param dir
	 */
	public static void renameFiles(File dir, int count) {
		//目录校验
		if (dir == null || ! dir.exists() || ! dir.isDirectory()) {
			return;
		}
		//获取并遍历所有子文件
		for (File f : listFiles(dir)) {
			//移动并重命名
			move(f, new File(dir, Objects.requireNonNull(FileNameUtil.addExtension((count++) + StringConstant.EMPTY, FileNameUtil.getExtension(f.getName())))));
		}
	}
	
	//################################ 文件读写 ################################//
	
	/**
	 * 读取文件为UTF-8的字符串
	 * 
	 * @param file
	 * @return 如果文件为空或不存在，返回null
	 * @throws IoRuntimeException  如果读取失败则抛出
	 */
	public static String readToStr(File file){
		if (file == null || ! file.exists()) {
			return null; 
		}
		try {
			//读取文件内容为UTF-8字符串，读取后关闭文件读取流
			return FileUtils.readFileToString(file,CharsetConstant.UTF8);
		} catch (IOException e) {
			throw new IoRuntimeException("文件读取为字符串失败："+ e.getMessage(), e);
		}
	}
	/**
	 * 读取文件为byte[]
	 * 
	 * @param file
	 * @return 如果文件为空或不存在，返回null
	 * @throws IoRuntimeException  如果读取失败则抛出
	 */
	public static byte[] readToBytes(File file){
		if (file == null || ! file.exists()) {
			return null; 
		}
		try {
			//读取文件内容为byte[]，读取后关闭文件读取流
			return FileUtils.readFileToByteArray(file);
		} catch (IOException e) {
			throw new IoRuntimeException("文件读取为字符串失败："+ e.getMessage(), e);
		}
	}
	/**
	 * 按行读取文件为UTF-8的字符串List
	 * 
	 * @param file
	 * @return 如果文件为空或不存在，返回null
	 * @throws IoRuntimeException  如果读取失败则抛出
	 */
	public static List<String> readToStrs(File file){
		if (file == null || ! file.exists()) {
			return null; 
		}
		try {
			//读取文件内容为UTF-8字符串，读取后关闭文件读取流
			return FileUtils.readLines(file,CharsetConstant.UTF8);
		} catch (IOException e) {
			throw new IoRuntimeException("文件读取为字符串集合失败："+ e.getMessage(), e);
		}
	}
	/**
	 * 以UTF-8输出字符串到文件，如果文件是目录则删除，如果文件不存在则创建，如果文件存在则覆盖
	 * 
	 * @param file
	 * @param str
	 * @throws IoRuntimeException  如果输出失败则抛出
	 */
	public static void writeStr(File file,String str){
		if (file == null || str == null) {
			return;
		}
		//如果已存在文件，并且是目录则强制删除，防止文件类型冲突
		if (file.exists() && file.isDirectory()) {
			delFile(file);
		}
		try {
			//将字符串写入文件：使用UTF-8的字符集；如果文件不存在则创建，存在则覆盖
			FileUtils.writeStringToFile(file, str, CharsetConstant.UTF8);
		} catch (IOException e) {
			throw new IoRuntimeException("写出字符串到文件失败："+ e.getMessage(), e);
		}
	}
	/**
	 * 以UTF-8输出字符串到文件末尾，如果文件是目录则删除，如果文件不存在则创建，如果文件存在则添加字符串到文件末尾
	 * 
	 * @param file
	 * @param str
	 * @throws IoRuntimeException  如果输出失败则抛出
	 */
	public static void writeStrAppend(File file,String str){
		if (file == null || str == null ) {
			return;
		}
		//如果已存在文件，并且是目录则强制删除，防止文件类型冲突
		if (file.exists() && file.isDirectory()) {
			delFile(file);
		}
		try {
			//将字符串写入文件末尾：使用UTF-8的字符集；如果文件不存在则创建，存在则附加到末尾
			FileUtils.writeStringToFile(file, str, CharsetConstant.UTF8);
		} catch (IOException e) {
			throw new IoRuntimeException("写出字符串到文件末尾失败："+ e.getMessage(), e);
		}
	}
	/**
	 * 输出byte[]到文件，如果文件是目录则删除，如果文件不存在则创建，如果文件存在则替换
	 * 
	 * @param file
	 * @param bytes
	 * @throws IoRuntimeException  如果输出失败则抛出
	 */
	public static void writeBytes(File file, byte[] bytes){
		if (file == null || bytes == null ) {
			return;
		}
		//如果已存在文件，并且是目录则强制删除，防止文件类型冲突
		if (file.exists() && file.isDirectory()) {
			delFile(file);
		}
		try {
			//将byte[]写入文件：如果文件不存在则创建，存在则覆盖
			FileUtils.writeByteArrayToFile(file,bytes);
		} catch (IOException e) {
			throw new IoRuntimeException("写出字节数组到文件失败："+ e.getMessage(), e);
		}
	}
	/**
	 * 输出byte[]到文件末尾，如果文件是目录则删除，如果文件不存在则创建，如果文件存在则添加数据到末尾
	 * 
	 * @param file
	 * @param bytes
	 * @throws IoRuntimeException  如果输出失败则抛出
	 */
	public static void writeBytesAppend(File file, byte[] bytes){
		if (file == null || bytes == null ) {
			return;
		}
		//如果已存在文件，并且是目录则强制删除，防止文件类型冲突
		if (file.exists() && file.isDirectory()) {
			delFile(file);
		}
		try {
			//将byte[]写入文件末尾：如果文件不存在则创建，存在则添加到文件末尾
			FileUtils.writeByteArrayToFile(file,bytes,true);
		} catch (IOException e) {
			throw new IoRuntimeException("写出字节数组到文件末尾失败："+ e.getMessage(), e);
		}
	}
	/**
	 * 以UTF-8逐行输出字符串集合到文件，如果文件是目录则删除，如果文件不存在则创建，如果文件存在则覆盖
	 * 
	 * @param file
	 * @param strs
	 * @throws IoRuntimeException  如果输出失败则抛出
	 */
	public static void writeStrs(File file,List<String> strs){
		if (file == null || strs == null || strs.isEmpty()) {
			return;
		}
		//如果已存在文件，并且是目录则强制删除，防止文件类型冲突
		if (file.exists() && file.isDirectory()) {
			delFile(file);
		}
		try {
			//将Collection<String>逐行写入文件：如果文件不存在则创建，存在则覆盖
			FileUtils.writeLines(file, CharsetConstant.UTF8, strs);
		} catch (IOException e) {
			throw new IoRuntimeException("写出字符串集合到文件失败："+ e.getMessage(), e);
		}
	}
	/**
	 * 以UTF-8逐行输出字符串集合到文件末尾，如果文件是目录则删除，如果文件不存在则创建，如果文件存在则逐行添加字符串到文件末尾
	 * 
	 * @param file
	 * @param strs
	 * @throws IoRuntimeException  如果输出失败则抛出
	 */
	public static void writeStrsAppend(File file,List<String> strs){
		if (file == null || strs == null || strs.isEmpty()) {
			return;
		}
		//如果已存在文件，并且是目录则强制删除，防止文件类型冲突
		if (file.exists() && file.isDirectory()) {
			delFile(file);
		}
		try {
			//将Collection<String>逐行写入文件：如果文件不存在则创建，存在则添加到末尾
			FileUtils.writeLines(file, CharsetConstant.UTF8, strs, true);
		} catch (IOException e) {
			throw new IoRuntimeException("写出字符串集合到文件末尾失败："+ e.getMessage(), e);
		}
	}
	 
	//################################  文件流 ################################//
	
	/**
	 * 获取文件的InputStream
	 * 
	 * @param file
	 * @return 如果文件为空、不存在、是目录则返回null
	 * @throws IoRuntimeException 如果获取InputStream失败则抛出
	 */
	public static FileInputStream getIns(File file){
		try {
			// 获取文件的 FileInputStream：如果文件不存在、是目录、不可读时抛出IOException
			return FileUtils.openInputStream(file);
		} catch (IOException e) {
			throw new IoRuntimeException("读取文件失败", e);
		}
	}
	
	/**
	 * 获取文件的OutputStream，如果文件存在则覆盖，如果不存在则创建必要的父目录及文件
	 * 
	 * @param file
	 * @return 如果文件为空、不存在、是目录则返回null
	 * @throws IoRuntimeException 如果获取OutputStream失败则抛出
	 */
	public static FileOutputStream getOut(File file){
		if (file == null || ! file.exists() || file.isDirectory()) {
			return null;
		}
		//创建文件必要的父目录，防止写出失败
		addParentDir(file);
		try {
			// 获取文件的FileOutputStream：如果文件存在则覆盖；如果文件不可创建、是目录、不可写时抛出IOException
			return FileUtils.openOutputStream(file);
		} catch (IOException e) {
			throw new IoRuntimeException("获取文件的OutputStream失败："+ e.getMessage(), e);
		}
	}
	
	/**
	 * 获取文件的OutputStream，如果文件存在则添加数据到末尾，如果不存在则创建必要的父目录及文件
	 * 
	 * @param file
	 * @return 如果文件为空、不存在、是目录则返回null
	 * @throws IoRuntimeException 如果获取OutputStream失败则抛出
	 */
	public static FileOutputStream getOutAppend(File file){
		if (file == null || ! file.exists() || file.isDirectory()) {
			return null;
		}
		//创建文件必要的父目录，防止写出失败
		addParentDir(file);
		try {
			// 获取文件的FileOutputStream：如果文件存在则添加数据到末尾；如果文件不可创建、是目录、不可写时抛出IOException
			return FileUtils.openOutputStream(file,true);
		} catch (IOException e) {
			throw new IoRuntimeException("获取文件添加数据的OutputStream失败："+ e.getMessage(), e);
		}
	}
	
	/**
	 * 读取文件并复制到OutputStream，返回复制的字节数
	 * 
	 * @param file
	 * @param out
	 * @return 如果文件为空不存在、Out为空则返回0
	 * @throws IoRuntimeException  如果读取或输出失败则抛出
	 */
	public static long readToOut(File file, OutputStream out){
		if (file == null || ! file.exists() || out == null) {
			return 0;
		}
		try {
			//复制文件内容到目标输出流，返回复制的字节数
			return FileUtils.copyFile(file,out);
		} catch (IOException e) {
			throw new IoRuntimeException("读取文件写到OutputStream失败："+ e.getMessage(), e);
		}
	} 
	/**
	 * 读取InputStream并写到文件，文件不存在会创建，存在则覆盖 
	 * 
	 * @param file
	 * @param ins
	 * @param autoClose 读取并写出后，是否自动关闭InputStream
	 * @throws IoRuntimeException  如果读取或输出失败则抛出
	 */
	public static void writeIns(File file, InputStream ins, boolean autoClose){
		if (file == null || ins == null) {
			return ;
		}
		//创建文件必要的父目录，防止写出失败
		addParentDir(file);
		try {
			if (autoClose) {
				//从输入流读取内容复制到文件：复制后输入流将关闭；文件若不存在则创建，存在则覆盖
				FileUtils.copyInputStreamToFile(ins,file);
			}else{
				//从输入流读取内容复制到文件：复制后不关闭输入流
				FileUtils.copyToFile(ins,file); 
			}
		} catch (IOException e) {
			throw new IoRuntimeException("读取InputStream写到文件失败："+ e.getMessage(), e);
		}
	}

	/**
	 * 去除Eclipse生成文件时，头部标记"\65279"信息，导致文件运行时报错"\65279 编码错误"
	 * 解决：将文件转换为ISO-8859-1，然后删除首个乱码字符，再重新写入文件
	 * 
	 * @param file Eclipse生成的文件
	 * 
	 * @throws IOException
	 */
	public static void cleanBom(File file) throws IOException {
		// 读取文件
		String s = FileUtils.readFileToString(file, CharsetConstant.ISO8859);
		// 去除"\65279"
		s = StringUtils.strip(s, ECLIPSE_BOM_UNI);
		s = StringUtils.strip(s, ECLIPSE_BOM);
		// 写入文件
		FileUtils.write(file, s, CharsetConstant.ISO8859);
	}
}
